/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.user.client.rpc;

/**
 * Base class for the client and server serialization streams.  This class handles
 * the basic serialization and desirialization formatting for primitive types
 * since these are common between the client and the server.
 * @skip
 */
public abstract class SerializationStream implements 
    SerializationStreamReader, SerializationStreamWriter,
    SerializationStreamObjectDecoder, SerializationStreamObjectEncoder, StringTable {

  public static final String NEW_INSTANCE_MARKER = "+";
  public static final String PREV_INSTANCE_MARKER = "#";
  public static final String NULL_INSTANCE_MARKER = "~";
  
  public static final int SERIALIZATION_STREAM_VERSION = 1;
  
  public static final int SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING = 1;
  
  protected int flags = 0;
  
  protected int version;
  
  public void addFlags(int flags) {
    this.flags |= flags;
  }
  
  public int getFlags() {
    return flags;
  }
  
  public void setFlags(int flags) {
    this.flags = flags;
  }
  
  protected void setVersion(int version) {
    this.version = version;
  }
  
  public int getVersion() {
    return version;
  }
  
  public boolean shouldEnforceTypeVersioning() {
    return (flags & SERIALIZATION_STREAM_FLAGS_NO_TYPE_VERSIONING) == 0;
  }
  
  protected abstract void append(String string);
  protected abstract String extract();
  
  public abstract void prepareToRead(String encodedString);
  public abstract void prepareToWrite();
  
  // SerializationStreamReader interface
  //
  public abstract SerializationStreamObjectDecoder getObjectDecoder();
  
  public boolean readBoolean() {
    return extract().equals("t");
  }

  public byte readByte() {
    return Byte.parseByte(extract());
  }

  /**
   * Character.MinValue and Character.MaxValue are special or cause special 
   * problems.  Character.MaxValue is used as the stream token delimiter so it 
   * is special cased here.  Character.MinValue is causes problems on the server 
   * when the payload of the request is converted to UTF8.  So, we escape them 
   * here
   */
  public char readChar() {
    String s = extract();
    if (s.equals("-1")) {
      return '\uffff';
    } else if (s.equals("0")) {
      return '\u0000';
    }
    
    return s.charAt(0);
  }

  public double readDouble() {
    return Double.parseDouble(extract());
  }

  public float readFloat() {
    return Float.parseFloat(extract());
  }

  public int readInt() {
    return Integer.parseInt(extract());
  }

  public long readLong() {
    return Long.parseLong(extract());
  }

  public short readShort() {
    return Short.parseShort(extract());
  }

  // SerializationStreamWriter interface
  //
  public abstract SerializationStreamObjectEncoder getObjectEncoder();
  
  public void writeBoolean(boolean fieldValue) {
    append(fieldValue ? "t" : "f");
  }

  public void writeByte(byte fieldValue) {
    append(String.valueOf(fieldValue));
  }

  /**
   * Character.MinValue and Character.MaxValue are special or cause special 
   * problems.  Character.MaxValue is used as the stream token delimiter so it 
   * is special cased here.  Character.MinValue is causes problems on the server 
   * when the payload of the request is converted to UTF8 (not really certain 
   * why).  So, we escape them here
   */
  public void writeChar(char ch) {
    switch (ch) {
      case Character.MIN_VALUE:
        append("0");
        break;
      case Character.MAX_VALUE:
        append("-1");
        break;
      default:
        append(String.valueOf(ch));
        break;
    }
  }

  public void writeDouble(double fieldValue) {
    append(String.valueOf(fieldValue));
  }

  public void writeFloat(float fieldValue) {
    append(String.valueOf(fieldValue));
  }

  public void writeInt(int fieldValue) {
    append(String.valueOf(fieldValue));
  }

  public void writeLong(long fieldValue) {
    append(String.valueOf(fieldValue));
  }
  
  public void writeShort(short value) {
    append(String.valueOf(value));
  }
}
